package cn.zhangfusheng.elasticsearch.model.analysis;

import cn.zhangfusheng.elasticsearch.annotation.document.field.FieldMapping;
import cn.zhangfusheng.elasticsearch.annotation.document.field.MappingParameters;
import cn.zhangfusheng.elasticsearch.annotation.document.field.parameters.Alias;
import cn.zhangfusheng.elasticsearch.annotation.document.field.parameters.FieldData;
import cn.zhangfusheng.elasticsearch.annotation.document.field.parameters.FieldDataFrequencyFilter;
import cn.zhangfusheng.elasticsearch.annotation.document.field.parameters.GeoPoint;
import cn.zhangfusheng.elasticsearch.annotation.document.field.parameters.IndexPrefixes;
import cn.zhangfusheng.elasticsearch.constant.enumeration.FieldType;
import cn.zhangfusheng.elasticsearch.exception.GlobalSystemException;
import cn.zhangfusheng.elasticsearch.model.annotation.DefaultFieldMapping;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.springframework.core.annotation.AnnotationUtils;

import java.io.IOException;
import java.lang.reflect.Field;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/**
 * @author fusheng.zhang
 * @date 2022-05-09 20:07:54
 */
public class AnalysisMapping {
    private final List<Field> entityDeclaredFields;

    public AnalysisMapping(List<Field> entityDeclaredFields) {
        this.entityDeclaredFields = entityDeclaredFields;
    }

    public void builder(XContentBuilder xContentBuilder) throws IOException {
        for (Field field : this.entityDeclaredFields) {
            FieldMapping fieldMapping =
                    Optional.ofNullable(field.getAnnotation(FieldMapping.class)).orElse(DefaultFieldMapping.INSTANCE);
            if (fieldMapping.ignore()) continue;
            boolean isDate = field.getType().equals(Date.class) || field.getType().equals(LocalDateTime.class);
            FieldType esFieldType = FieldType.Text;
            if (isDate) {
                esFieldType = FieldType.Date;
            } else if (Objects.nonNull(fieldMapping.type())) {
                if (fieldMapping.type().equals(FieldType.Default)) {
                    esFieldType = getEsType(field.getType().getSimpleName());
                } else {
                    esFieldType = fieldMapping.type();
                }
            }
            xContentBuilder.startObject(field.getName());
            this.builderType(xContentBuilder, esFieldType);
            this.builderParameters(fieldMapping.mappingParameters(), xContentBuilder);
            this.builderGeoPoint(fieldMapping.geoPoint(), esFieldType, xContentBuilder);
            this.builderAlias(fieldMapping.alias(), esFieldType, xContentBuilder);
            xContentBuilder.endObject();
        }
    }

    private void builderAlias(Alias alias, FieldType esFieldType, XContentBuilder xContentBuilder) throws IOException {
        if (!Objects.equals(esFieldType, FieldType.Alias) || Objects.isNull(alias)) return;
        Map<String, Object> annotationAttributes = AnnotationUtils.getAnnotationAttributes(alias);
        for (Map.Entry<String, Object> entry : annotationAttributes.entrySet()) {
            String k = entry.getKey();
            Object v = entry.getValue();
            if (v instanceof String && StringUtils.isNotBlank(String.valueOf(v))) xContentBuilder.field(k, v);
        }
    }

    /**
     * GeoPoint 补充属性解析
     * @param geoPoint
     * @param esFieldType
     * @param xContentBuilder
     * @throws IOException
     */
    private void builderGeoPoint(GeoPoint geoPoint, FieldType esFieldType, XContentBuilder xContentBuilder) throws IOException {
        if (!Objects.equals(esFieldType, FieldType.Geo_Point) || Objects.isNull(geoPoint)) return;
        Map<String, Object> annotationAttributes = AnnotationUtils.getAnnotationAttributes(geoPoint);
        for (String key : annotationAttributes.keySet()) {
            Object value = annotationAttributes.get(key);
            if (value instanceof Long) {
                long vl = (Long) value;
                if (vl != -1) xContentBuilder.field(key, vl == 1L);
            } else if (value instanceof String) {
                if (StringUtils.isNotBlank(String.valueOf(value))) xContentBuilder.field(key, value);
            }
        }
    }

    private void builderParameters(MappingParameters parameter, XContentBuilder xContentBuilder) throws IOException {
        if (Objects.isNull(parameter)) return;
        Map<String, Object> annotationAttributes = AnnotationUtils.getAnnotationAttributes(parameter);
        for (String k : annotationAttributes.keySet()) {
            Object v = annotationAttributes.get(k);
            if (v instanceof String) {
                if (StringUtils.isNotBlank(String.valueOf(v))) xContentBuilder.field(k, v);
            } else if (v instanceof Long) {
                if (!Objects.equals(v, -1L)) xContentBuilder.field(k, v.equals(1L));
            } else if (v instanceof Integer) {
                if (!Objects.equals(v, -1)) xContentBuilder.field(k, v);
            } else if (v instanceof Class<?>) {
                if (!Objects.equals(v, Void.class)) this.builderWithClass(xContentBuilder, k, (Class<?>) v);
            } else if (v.getClass().isArray()) {
                Class<?> componentType = v.getClass().getComponentType();
                if (componentType.equals(String.class)) {
                    String[] strs = (String[]) v;
                    if (strs.length > 0) {
                        Object[] objects = Arrays.stream(strs).filter(StringUtils::isNotBlank).toArray();
                        if (objects.length > 0) xContentBuilder.field(k, objects);
                    }
                } else if (componentType.equals(FieldData.class)) {
                    this.builderFieldData(xContentBuilder, k, (FieldData[]) v);
                } else if (componentType.equals(IndexPrefixes.class)) {
                    this.builderIndexPrefixes(xContentBuilder, k, (IndexPrefixes[]) v);
                }
            }
        }
    }

    /**
     * 解析 class 类型 字段
     * @param xContentBuilder
     * @param key
     * @param aClass
     * @throws IOException
     */
    private void builderWithClass(XContentBuilder xContentBuilder, String key, Class<?> aClass) throws IOException {
        if (Objects.equals(key, "fields") || Objects.equals(key, "properties")) {
            Field[] declaredFields = aClass.getDeclaredFields();
            if (declaredFields.length > 0) {
                xContentBuilder.startObject(key);
                new AnalysisMapping(Arrays.asList(declaredFields)).builder(xContentBuilder);
                xContentBuilder.endObject();
            }
        }
    }

    private void builderIndexPrefixes(XContentBuilder xContentBuilder, String key, IndexPrefixes[] indexPrefixes) throws IOException {
        if (indexPrefixes.length == 0) return;
        if (indexPrefixes.length > 1) {
            throw new GlobalSystemException("MappingParameters.indexPrefixes lenght >1");
        }
        Map<String, Object> indexPrefixAttributes = AnnotationUtils.getAnnotationAttributes(indexPrefixes[0]);
        xContentBuilder.startObject(key);
        for (String k : indexPrefixAttributes.keySet()) {
            xContentBuilder.field(k, indexPrefixAttributes.get(k));
        }
        xContentBuilder.endObject();
    }

    private void builderFieldData(XContentBuilder xContentBuilder, String key, FieldData[] fieldData) throws IOException {
        if (fieldData.length == 0) return;
        if (fieldData.length > 1) {
            throw new GlobalSystemException("MappingParameters.fielddata lenght >1");
        }
        xContentBuilder.field(key, fieldData[0].fielddata());
        FieldDataFrequencyFilter[] fieldDataFrequencyFilters = fieldData[0].fielddata_frequency_filter();
        if (fieldDataFrequencyFilters.length == 1) {
            xContentBuilder.startObject("fielddata_frequency_filter");
            FieldDataFrequencyFilter fieldDataFrequencyFilter = fieldDataFrequencyFilters[0];
            Map<String, Object> fieldDataFrequencyFilterAttributes = AnnotationUtils.getAnnotationAttributes(fieldDataFrequencyFilter);
            for (String k : fieldDataFrequencyFilterAttributes.keySet()) {
                xContentBuilder.field(k, fieldDataFrequencyFilterAttributes.get(k));
            }
            xContentBuilder.endObject();
        } else if (fieldDataFrequencyFilters.length > 1) {
            throw new GlobalSystemException("MappingParameters.fielddata.fielddata_frequency_filter lenght >1");
        }
    }

    @SuppressWarnings("UnusedReturnValue")
    private XContentBuilder builderType(XContentBuilder xContentBuilder, FieldType fieldType) throws IOException {
        switch (fieldType) {
            case Text: return xContentBuilder.field("type", "text");
            case Keyword: return xContentBuilder.field("type", "keyword");
            case Long: return xContentBuilder.field("type", "long");
            case Integer: return xContentBuilder.field("type", "integer");
            case Short: return xContentBuilder.field("type", "short");
            case Byte: return xContentBuilder.field("type", "byte");
            case Double: return xContentBuilder.field("type", "double");
            case Float: return xContentBuilder.field("type", "float");
            case Half_Float: return xContentBuilder.field("type", "half_float");
            case Scaled_Float: return xContentBuilder.field("type", "scaled_float");
            case Boolean: return xContentBuilder.field("type", "boolean");
            case Binary: return xContentBuilder.field("type", "binary");
            case Date: return xContentBuilder.field("type", "date");
            case Date_Nanos: return xContentBuilder.field("type", "date_nanos");
            case Integer_Range: return xContentBuilder.field("type", "integer_range");
            case Float_Range: return xContentBuilder.field("type", "float_range");
            case Long_Range: return xContentBuilder.field("type", "long_range");
            case Double_Range: return xContentBuilder.field("type", "double_range");
            case Date_Range: return xContentBuilder.field("type", "date_range");

            case Object: return xContentBuilder;
            case Nested: return xContentBuilder.field("type", "nested");

            case Geo_Point: return xContentBuilder.field("type", "geo_point");

            case Flattened: return xContentBuilder.field("type", "flattened");
            case Alias: return xContentBuilder.field("type", "alias");
            case Arrays: return xContentBuilder;
            case Wildcard: return xContentBuilder.field("type", "wildcard");
            default: throw new GlobalSystemException("");
        }
    }

    /**
     * 获取es字段类型
     * @param fieldTypeName
     * @return
     */
    private FieldType getEsType(String fieldTypeName) {
        switch (fieldTypeName.toLowerCase()) {
            case "date": case "localdatetime": return FieldType.Date;
            case "string": return FieldType.Text;
            case "boolean": return FieldType.Boolean;
            case "int": case "integer": return FieldType.Integer;
            case "long": return FieldType.Long;
            case "short": return FieldType.Short;
            case "byte": return FieldType.Byte;
            case "double": return FieldType.Double;
            case "float": return FieldType.Float;
            default: throw new GlobalSystemException("暂不支持的类型[{}],匹配失败", fieldTypeName);
        }
    }
}
